甚麼是「Callback function」?
MDN的解釋如下:
「回呼函式(callback function)是指能藉由參數(argument)通往另一個函式的函式。它會在外部函式內調用、以完成某些事情。」
我改寫一下MDN上的範例:
function aFunc(name) {
alert('Hello ' + name);
}
function bFunc(callback) {
var name = prompt('報名華山論劍大會,請輸入你的名字:');
callback(name);
}
bFunc(aFunc);
最後呼叫函式 bFunc (aFunc),aFunc是bFunc的callback參數,bFunc的變數name又藉由callback傳入bFunc之中。所以我們可以理解「把A函式當成B函式的參數,透過B函式來呼叫它」,A函式就是一個Callback function。
解釋有點抽象嗎?讓我們換個場景,想想之前提過的「事件監聽」。
例如:看到紅燈,然後踩剎車!踩剎車這個動作,在「看到紅燈」這個條件滿足的時候才執行。所以我們會監聽「看到紅燈」這個事件,一旦事件觸發,就去呼叫「剎車」這個動作(函式)。這也是把「剎車」這個函式當成事件監聽的參數。
還有一個常常會用到的window.setTimeout()也是callback function的經典案例:
window.setTimeout(function(){//do something},1000);
所以我們可以歸納出:
那在甚麼時候適合使用callback function呢?我想是在「控制函式執行的時機」的情境下適合使用:
假設郭靖跟歐陽克都中了毒:
let poisonA = function(){
alert('歐陽克中毒身亡!');
};
let poisonB = function(){
alert('郭靖中毒身亡!');
}
poisonA();
poisonB();
這樣的執行順序當然是先跳('歐陽克中毒身亡!')的視窗,再跳('郭靖中毒身亡!')。但是如果加上一個隨機生成的等待時間,那視窗的彈跳順序就不一定了。
let poisonA = function(){
var i = Math.random()+1;
window.setTimeout(function () {
alert('歐陽克中毒身亡!');
}, i * 1000)
};
let poisonB = function(){
var i = Math.random()+1;
window.setTimeout( () {
alert('郭靖中毒身亡!');
}, i * 1000 )
}
poisonA();
poisonB();
有時是('郭靖中毒身亡!')會先跳出來,有時是('歐陽克中毒身亡!')會先跳出來!
如果我們想確保('歐陽克中毒身亡!')比('郭靖中毒身亡!')早跳出來,可以這樣寫:
const poisonA = function(callback){
const i = Math.random() + 1;
window.setTimeout(function () {
alert('歐陽克中毒身亡!');
if (typeof callback === 'function'){
callback();
};
}, i * 1000)
};
const poisonB = function(){
const i = Math.random() + 1;
window.setTimeout (function() {
alert('郭靖中毒身亡!');
}, i* 1000 );
}
poisonA( poisonB );
這樣歐陽克就會比郭靖還要早毒發身亡了!
但是如果中毒的人越來越多,一個函式呼叫另一個函式,一層一層包下去,就變成「回呼地獄」了。
再假設另外一個情境:
「王重陽參加華山論劍,只要打敗黃藥師、洪七公、段皇爺與歐陽鋒,就會奪得『武功第一』的封號。但是不用去管王重陽與人決鬥的先後順序,只要與每個人都打過就可以。」
這時候我們可以這樣做:
let fightProcess = []; //設一個空陣列,王重陽每次比武,都push到陣列中
let step = 4; //王重陽與4個人比武
function fightA () {
window.setTimeout(function(){
fightProcess.push('王重陽打敗黃藥師');
console.log('王重陽打敗黃藥師');
if (fightProcess.length === step){
//比較空陣列fightProcess的長度是否與step相等,如果相等,就執行ightWinner()
fightWinner();
}
},(Math.random()+1) * 1000);
};
function fightB () {
window.setTimeout(function(){
fightProcess.push('王重陽打敗洪七公');
console.log('王重陽打敗洪七公');
if (fightProcess.length === step){
fightWinner();
}
},(Math.random()+1) * 1000);
}
function fightC () {
window.setTimeout(function(){
fightProcess.push('王重陽打敗段皇爺');
console.log('王重陽打敗段皇爺');
if (fightProcess.length === step){
fightWinner();
}
},(Math.random()+1) * 1000);
}
function fightD () {
window.setTimeout(function(){
fightProcess.push('王重陽打敗歐陽鋒');
console.log('王重陽打敗歐陽鋒');
if (fightProcess.length === step){
fightWinner();
}
},(Math.random()+1) * 1000);
}
function fightWinner(){
console.log('王重陽天下武功第一,人稱「中神通」');
console.log(fightProcess);
}
fightA();
fightB();
fightC();
fightD();
Promise物件是ES6之後新增的物件,照字面的解釋就是「承諾」,回傳的結果只有兩種:「解決」與「拒絕」。
Promise物件長成這個樣子:
let myPromise = new Promise((resolve, reject) =>{
resolve('解決');
reject('拒絕');
})
要在一個函式中使用Promise功能,只要讓它回傳一個Promise物件就行了:
function urPromise(){
return new Promise((resolve,reject) => {
//resolve()或reject()
});
}
Promise還提供了三種方法:
像剛剛那個「王重陽與四大高手比武」的過程就可以這樣寫:
function fightA () {
return new Promise(function(resolve,reject){
window.setTimeout(function() {
console.log('王重陽打敗黃藥師');
resolve('王重陽打敗黃藥師');
},(Math.random()+1) * 1000);
});
}
function fightB () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗洪七公');
resolve('王重陽打敗洪七公');
},(Math.random()+1) * 1000);
});
}
function fightC () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗段皇爺');
resolve('王重陽打敗段皇爺');
},(Math.random()+1) * 1000);
});
}
function fightD () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗歐陽鋒');
resolve('王重陽打敗歐陽鋒');
},(Math.random()+1) * 1000);
});
}
function fightWinner(){
console.log('王重陽天下武功第一,人稱「中神通」');
}
//加上.then可以做到依次執行
fightA()
.then(fightB)
.then(fightC)
.then(fightD)
.then(fightWinner);
我們在呼叫fightA()之後,用.then串接後面要執行的函式,這樣我們就可以做到依順序執行了。
來看看promise.all的情況:
function fightA () {
return new Promise(function(resolve,reject){
window.setTimeout(function() {
console.log('王重陽打敗黃藥師');
resolve('王重陽打敗黃藥師');
},(Math.random()+1) * 1000);
});
}
function fightB () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗洪七公');
resolve('王重陽打敗洪七公');
},(Math.random()+1) * 1000);
});
}
function fightC () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗段皇爺');
resolve('王重陽打敗段皇爺');
},(Math.random()+1) * 1000);
});
}
function fightD () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗歐陽鋒');
resolve('王重陽打敗歐陽鋒');
},(Math.random()+1) * 1000);
});
}
function fightWinner(){
console.log('王重陽天下武功第一,人稱「中神通」');
}
//不管fightA(),fightB(),fightC(),fightD()的執行順序,只要都執行了就繼續後面的程式
Promise.all([fightA(),fightB(),fightC(),fightD()])
.then(fightWinner);
Promise.all()則會等待全部的Promise函式都執行了,才會進行後面的.then函式。
然後是promise.race:
function fightA () {
return new Promise(function(resolve,reject){
window.setTimeout(function() {
console.log('王重陽打敗黃藥師');
resolve('王重陽打敗黃藥師');
},(Math.random()+1) * 1000);
});
}
function fightB () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗洪七公');
resolve('王重陽打敗洪七公');
},(Math.random()+1) * 1000);
});
}
function fightC () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗段皇爺');
resolve('王重陽打敗段皇爺');
},(Math.random()+1) * 1000);
});
}
function fightD () {
return new Promise((resolve,reject) =>{
window.setTimeout(() =>{
console.log('王重陽打敗歐陽鋒');
resolve('王重陽打敗歐陽鋒');
},(Math.random()+1) * 1000);
});
}
function fightWinner(){
console.log('王重陽天下武功第一,人稱「中神通」');
}
//只要fightA(),fightB(),fightC(),fightD()其中之一執行,就繼續執行後面的程式
//但是fightA(),fightB(),fightC(),fightD()都會執行,不會取消
Promise.race([fightA(),fightB(),fightC(),fightD()])
.then(fightWinner);
Promise.race就如同「競賽」一樣,只要有其中一個Promise函式先做到,不待其它的Promise函式完成,就直接進行.then後面的程式。但是其他的Promise函式還是會繼續執行,不會取消。